/*
  author Sylvain Bertrand <sylvain.bertrand@gmail.com>
  Protected by linux GNU GPLv2
  Copyright 2012-2014
*/
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/firmware.h>
#include <linux/cdev.h>
#include <linux/delay.h>

#include <alga/rng_mng.h>
#include <uapi/alga/pixel_fmts.h>
#include <alga/timing.h>
#include <alga/amd/atombios/atb.h>
#include <uapi/alga/amd/dce6/dce6.h>

#include "mc.h"
#include "rlc.h"
#include "ih.h"
#include "fence.h"
#include "ring.h"
#include "dmas.h"
#include "ba.h"
#include "cps.h"
#include "gpu.h"
#include "drv.h"

#include "ucode.h"

#include "regs.h"

#include "rlc_clearstate.h"

#define RLC_FW_DWS	2048

MODULE_FIRMWARE("radeon/TAHITI_rlc.bin");
MODULE_FIRMWARE("radeon/PITCAIRN_rlc.bin");
MODULE_FIRMWARE("radeon/VERDE_rlc.bin");
MODULE_FIRMWARE("radeon/OLAND_rlc.bin");

static u32 verde_save_restore_regs[] =
{
	(0x8000 << 16) | (0x98f4 >> 2),
	0x00000000,
	(0x8040 << 16) | (0x98f4 >> 2),
	0x00000000,
	(0x8000 << 16) | (0xe80 >> 2),
	0x00000000,
	(0x8040 << 16) | (0xe80 >> 2),
	0x00000000,
	(0x8000 << 16) | (0x89bc >> 2),
	0x00000000,
	(0x8040 << 16) | (0x89bc >> 2),
	0x00000000,
	(0x8000 << 16) | (0x8c1c >> 2),
	0x00000000,
	(0x8040 << 16) | (0x8c1c >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x98f0 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0xe7c >> 2),
	0x00000000,
	(0x8000 << 16) | (0x9148 >> 2),
	0x00000000,
	(0x8040 << 16) | (0x9148 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x9150 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x897c >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x8d8c >> 2),
	0x00000000,
	(0x9c00 << 16) | (0xac54 >> 2),
	0X00000000,
	0x3,
	(0x9c00 << 16) | (0x98f8 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x9910 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x9914 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x9918 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x991c >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x9920 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x9924 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x9928 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x992c >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x9930 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x9934 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x9938 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x993c >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x9940 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x9944 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x9948 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x994c >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x9950 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x9954 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x9958 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x995c >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x9960 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x9964 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x9968 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x996c >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x9970 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x9974 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x9978 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x997c >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x9980 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x9984 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x9988 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x998c >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x8c00 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x8c14 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x8c04 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x8c08 >> 2),
	0x00000000,
	(0x8000 << 16) | (0x9b7c >> 2),
	0x00000000,
	(0x8040 << 16) | (0x9b7c >> 2),
	0x00000000,
	(0x8000 << 16) | (0xe84 >> 2),
	0x00000000,
	(0x8040 << 16) | (0xe84 >> 2),
	0x00000000,
	(0x8000 << 16) | (0x89c0 >> 2),
	0x00000000,
	(0x8040 << 16) | (0x89c0 >> 2),
	0x00000000,
	(0x8000 << 16) | (0x914c >> 2),
	0x00000000,
	(0x8040 << 16) | (0x914c >> 2),
	0x00000000,
	(0x8000 << 16) | (0x8c20 >> 2),
	0x00000000,
	(0x8040 << 16) | (0x8c20 >> 2),
	0x00000000,
	(0x8000 << 16) | (0x9354 >> 2),
	0x00000000,
	(0x8040 << 16) | (0x9354 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x9060 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x9364 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x9100 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x913c >> 2),
	0x00000000,
	(0x8000 << 16) | (0x90e0 >> 2),
	0x00000000,
	(0x8000 << 16) | (0x90e4 >> 2),
	0x00000000,
	(0x8000 << 16) | (0x90e8 >> 2),
	0x00000000,
	(0x8040 << 16) | (0x90e0 >> 2),
	0x00000000,
	(0x8040 << 16) | (0x90e4 >> 2),
	0x00000000,
	(0x8040 << 16) | (0x90e8 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x8bcc >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x8b24 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x88c4 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x8e50 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x8c0c >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x8e58 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x8e5c >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x9508 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x950c >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x9494 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0xac0c >> 2),
	0x00000000,
	(0x9c00 << 16) | (0xac10 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0xac14 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0xae00 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0xac08 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x88d4 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x88c8 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x88cc >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x89b0 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x8b10 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x8a14 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x9830 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x9834 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x9838 >> 2),
	0x00000000,
	(0x9c00 << 16) | (0x9a10 >> 2),
	0x00000000,
	(0x8000 << 16) | (0x9870 >> 2),
	0x00000000,
	(0x8000 << 16) | (0x9874 >> 2),
	0x00000000,
	(0x8001 << 16) | (0x9870 >> 2),
	0x00000000,
	(0x8001 << 16) | (0x9874 >> 2),
	0x00000000,
	(0x8040 << 16) | (0x9870 >> 2),
	0x00000000,
	(0x8040 << 16) | (0x9874 >> 2),
	0x00000000,
	(0x8041 << 16) | (0x9870 >> 2),
	0x00000000,
	(0x8041 << 16) | (0x9874 >> 2),
	0x00000000,
	0x00000000
};

static long verde_init(struct pci_dev *dev)
{
	struct dev_drv_data *dd;
	long r;
	u32 reg;
	u64 save_restore_gpu_addr;

	dd = pci_get_drvdata(dev);

	r = rng_alloc_align(&dd->rlc.save_restore, &dd->vram.mng,
			sizeof(verde_save_restore_regs), GPU_PAGE_SZ);
	if (r != 0) {
		dev_err(&dev->dev,
				"unable to alloc GPU RLC save restore page\n");
		return -SI_ERR;
	}

	save_restore_gpu_addr = dd->rlc.save_restore;
	for (reg = 0; reg < ARRAY_SIZE(verde_save_restore_regs); ++reg) {
		/* XXX: endianness may be a trap here */
		vram_w32(dev, verde_save_restore_regs[reg],
							save_restore_gpu_addr);
		save_restore_gpu_addr += 4;
	}
	return 0;
}

long rlc_ucode_load(struct pci_dev *dev)
{
	struct dev_drv_data *dd;

	dd = pci_get_drvdata(dev);
	return ucode_load(dev, &(dd->rlc_fw), "rlc", RLC_FW_DWS);
}

void rlc_ucode_program(struct pci_dev *dev)
{
	u32 i;
	const __be32 *fw_data;
	struct dev_drv_data *dd;

	dd = pci_get_drvdata(dev);

	wr32(dev, 0, RLC_RL_BASE);
	wr32(dev, 0, RLC_RL_SZ);
	wr32(dev, 0, RLC_LB_CTL);
	wr32(dev, 0xffffffff, RLC_LB_CNTR_MAX);
	wr32(dev, 0, RLC_LB_CNTR_INIT);

	wr32(dev, 0, RLC_MC_CTL);
	wr32(dev, 0, RLC_UCODE_CTL);

	fw_data = (const __be32 *)dd->rlc_fw->data;
	for (i = 0; i < RLC_FW_DWS; ++i) {
		wr32(dev, i, RLC_UCODE_ADDR);
		wr32(dev, be32_to_cpup(fw_data++), RLC_UCODE_DATA); 
	}
	wr32(dev, 0, RLC_UCODE_ADDR);
}

void rlc_serdes_wait(struct pci_dev *dev)
{
	u32 i;

	for (i = 0; i < USEC_TIMEOUT; ++i) {
		u32 rlc_serdes_master_busy_0;

		rlc_serdes_master_busy_0 = rr32(dev, RLC_SERDES_MASTER_BUSY_0);
		if (rlc_serdes_master_busy_0 == 0)
			break;
		udelay(1);
	}

	for (i = 0; i < USEC_TIMEOUT; ++i) {
		u32 rlc_serdes_master_busy_1;

		rlc_serdes_master_busy_1 = rr32(dev, RLC_SERDES_MASTER_BUSY_1);
		if (rlc_serdes_master_busy_1 == 0)
			break;
		udelay(1);
	}
}

void rlc_wait(struct pci_dev *dev)
{
	u32 mask;
	u32 i;

	mask = RS_RLC_BUSY_STATUS | RS_GFX_PWR_STATUS | RS_GFX_CLK_STATUS
							| RS_GFX_LS_STATUS;
	for (i = 0; i < USEC_TIMEOUT; ++i) {
		u32 rlc_stat;

		rlc_stat = rr32(dev, RLC_STAT);
		if ((rlc_stat & mask) == (RS_GFX_CLK_STATUS
							| RS_GFX_PWR_STATUS))
			break;
		udelay(1);
	}
}

void rlc_reset(struct pci_dev *dev)
{
	u32 grbm_soft_reset;

	grbm_soft_reset = rr32(dev, GRBM_SOFT_RESET);

	grbm_soft_reset |= GSR_SOFT_RESET_RLC;
	wr32(dev, grbm_soft_reset, GRBM_SOFT_RESET);
	udelay(50);

	grbm_soft_reset &= ~GSR_SOFT_RESET_RLC;
	wr32(dev, grbm_soft_reset, GRBM_SOFT_RESET);
	udelay(50);
}

void rlc_update_ctl(struct pci_dev *dev, u32 prev_rlc_ctl)
{
	u32 cur_rlc_ctl;

	cur_rlc_ctl = rr32(dev, RLC_CTL);
	if (cur_rlc_ctl != prev_rlc_ctl)
		wr32(dev, prev_rlc_ctl, RLC_CTL);
}

static u32 rlc_dis(struct pci_dev *dev)
{
	u32 rlc_ctl;

	rlc_ctl = rr32(dev, RLC_CTL);
	
	if (rlc_ctl & RC_RLC_ENA) {
		rlc_ctl &= ~RC_RLC_ENA;
		wr32(dev, rlc_ctl, RLC_CTL);

		rlc_serdes_wait(dev);
	}
	return rlc_ctl;
}

void rlc_shutdown(struct pci_dev *dev)
{
	wr32(dev, 0, RLC_CTL);
}

void rlc_ena(struct pci_dev *dev)
{
	wr32(dev, RC_RLC_ENA, RLC_CTL);
}

void rlc_cleanup(struct pci_dev *dev)
{
	struct dev_drv_data *dd;
	dd = pci_get_drvdata(dev);

	if (dd->family == VERDE)
		rng_free(&dd->vram.mng, dd->rlc.save_restore);
	rng_free(&dd->vram.mng, dd->rlc.clr_restore);
}

long rlc_init(struct pci_dev *dev)
{
	struct dev_drv_data *dd;
	long r;

	dd = pci_get_drvdata(dev);

	if (dd->family == VERDE) {
		r = verde_init(dev);
		if (r == -SI_ERR)
			return -SI_ERR;
	}

	r = clearstate_init(dev);
	if (r == -SI_ERR)
		goto err_free_save_restore;
	return 0;

err_free_save_restore:
	if (dd->family == VERDE)
		rng_free(&dd->vram.mng, dd->rlc.save_restore);
	return -SI_ERR;
}

void rlc_serdes_mgcg_dis(struct pci_dev *dev)
{
	u32 reg_rlc_ctl;

	reg_rlc_ctl = rlc_dis(dev);

	wr32(dev, 0xffffffff, RLC_SERDES_WR_MASTER_MASK_0);
	wr32(dev, 0xffffffff, RLC_SERDES_WR_MASTER_MASK_1);
	wr32(dev, 0x00e000ff, RLC_SERDES_WR_CTL);

	rlc_update_ctl(dev, reg_rlc_ctl);
}

void rlc_serdes_mgcg_ena(struct pci_dev *dev)
{
	u32 reg_rlc_ctl;

	reg_rlc_ctl = rlc_dis(dev);

	wr32(dev, 0xffffffff, RLC_SERDES_WR_MASTER_MASK_0);
	wr32(dev, 0xffffffff, RLC_SERDES_WR_MASTER_MASK_1);
	wr32(dev, 0x00d000ff, RLC_SERDES_WR_CTL);

	rlc_update_ctl(dev, reg_rlc_ctl);
}

void rlc_mgcg_ena(struct pci_dev *dev)
{
	u32 cur;
	u32 want;
	
	cur = rr32(dev, RLC_CGTT_MGCG_OVERRIDE);
	want = cur & 0xffffffc0;
	if (cur != want)
		wr32(dev, want, RLC_CGTT_MGCG_OVERRIDE);
}

void rlc_mgcg_dis(struct pci_dev *dev)
{
	u32 cur;
	u32 want;

	cur = rr32(dev, RLC_CGTT_MGCG_OVERRIDE);
	want = cur | 0x00000003;
	if (cur != want)
		wr32(dev, want, RLC_CGTT_MGCG_OVERRIDE);
}

void rlc_cgcg_ena(struct pci_dev *dev)
{
	wr32(dev, 0x00000080, RLC_GCPM_GENERAL_3);
}

void rlc_serdes_cgcg_ena(struct pci_dev *dev)
{
	u32 rlc_ctl;
	rlc_ctl = rlc_dis(dev);

	wr32(dev, 0xffffffff, RLC_SERDES_WR_MASTER_MASK_0);
	wr32(dev, 0xffffffff, RLC_SERDES_WR_MASTER_MASK_1);
	wr32(dev, 0x00b000ff, RLC_SERDES_WR_CTL);

	rlc_serdes_wait(dev);

	rlc_update_ctl(dev, rlc_ctl);

	wr32(dev, 0x007000ff, RLC_SERDES_WR_CTL);
}

void rlc_lb_pw_dis(struct pci_dev *dev)
{
	u32 rlc_lb_ctl;
	rlc_lb_ctl = rr32(dev, RLC_LB_CTL);
	rlc_lb_ctl &= ~RLC_LB_ENA;
	wr32(dev, rlc_lb_ctl, RLC_LB_CTL);
}

void rlc_lb_pw_ena(struct pci_dev *dev)
{
	u32 rlc_lb_ctl;
	rlc_lb_ctl = rr32(dev, RLC_LB_CTL);
	rlc_lb_ctl |= RLC_LB_ENA;
	wr32(dev, rlc_lb_ctl, RLC_LB_CTL);
}
